Skip to content

feat: add dark mode toggle with localStorage persistence#1109

Open
karkera-saakshi wants to merge 5 commits into
SB2318:webfrom
karkera-saakshi:feature/dark-mode-toggle
Open

feat: add dark mode toggle with localStorage persistence#1109
karkera-saakshi wants to merge 5 commits into
SB2318:webfrom
karkera-saakshi:feature/dark-mode-toggle

Conversation

@karkera-saakshi

Copy link
Copy Markdown

PR Description

Closes #930 Add Dark Mode Support for Better Visibility and User Experience

What's Changed

  • Installed and configured next-themes for dark mode support
  • Created ThemeProvider component following the shadcn/ui dark mode guide
  • Wrapped root layout with ThemeProvider with attribute="class", defaultTheme="system", and enableSystem
  • Added suppressHydrationWarning to tag to prevent FOUC (Flash of Unstyled Content)
  • Added sun/moon toggle button in the navbar
  • Theme preference is persisted in localStorage — refreshing the page keeps the selected theme
  • Full dark mode coverage across all pages and components including:
  1. Navbar
  2. Hero section
  3. Feature cards
  4. Screenshots section
  5. Contact section
  6. Input fields

Testing

  • Toggle works from the navbar on all pages
  • Theme persists after page refresh
  • No flash of unstyled content on load

Type of Change

  • Bug fix (change which fixes an issue)
  • New feature (change which adds functionality)
  • Documentation update

Select your work-area

  • Frontend
  • Backend
  • Documentation
  • Others

Related Issue

Please provide a link to the issue solved if applicable.

Add your Work Example

Showcasing light and dark mode

darkmode.1.mp4

Reloading the page while in dark or light mode

reloading.mp4

Opening the same web in new tab to see it still ahs the same setting for modes as before

new.tab.mp4

Fixes (mention the issue number which this fixes)

#930

Checklist

  • I have updated my branch and synced it with the project's 'develop' branch before making this PR.
  • I have optimized the file changes.
  • I have added a snapshot of my work example.
  • I have made a PR to the project's develop branch.

Undertaking

  • My code follows the style guidelines of this project.

  • I have performed a self-review of my code.

  • I have commented my code, particularly in hard-to-understand areas.

  • I have made corresponding changes to the documentation.

  • I have checked for plagiarism and assure its authenticity.

  • I have read and followed the code of conduct for this repository. I understand that violation of this undertaking may have legal consequences.

  • I Agree

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Thank you @, for creating the PR and contributing to our UltimateHealth project 💗.
Our team will review the PR and will reach out to you soon! 😇
Make sure that you have marked all the tasks that you are done with ✅.
Thank you for your patience! 😀

@SB2318 SB2318 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your Contribution!

@SB2318

SB2318 commented Jun 5, 2026

Copy link
Copy Markdown
Owner

/review

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

🤖 Gemini AI Code Review

Summary

This Pull Request introduces dark mode functionality to the application using next-themes and localStorage for persistence, aligning with the shadcn/ui dark mode guide. It includes a theme toggle in the navbar and aims for full dark mode coverage across various sections and components. The PR correctly implements ThemeProvider and suppressHydrationWarning to prevent FOUC.

While the intention and initial setup are good, the implementation suffers from several critical issues related to maintainability, potential regressions, and styling best practices, primarily an over-reliance on !important in the CSS.

🔴 High Severity

  • Issue: Metadata Loss in layout.tsx
    The metadata object in web/src/app/layout.tsx has had keywords and openGraph properties removed.

    --- a/web/src/app/layout.tsx
    +++ b/web/src/app/layout.tsx
    @@ -10,12 +10,7 @@
     
     export const metadata: Metadata = {
       title: "UltimateHealth - Empowering Wellness Through Global Community",
    -  description:
    -    "UltimateHealth is a platform that lets you publish health knowledge in your own language, review content, and share podcasts with the world.",
    -  keywords: "health, wellness, community, articles, podcasts, multilingual",
    -  openGraph: {
    -    title: "UltimateHealth",
    -    description: "Empowering Wellness Through Global Community",
    -    type: "website",
    -  },
    +  description: "UltimateHealth platform description.",
     };

    Impact: This is a significant regression for Search Engine Optimization (SEO) and social media sharing. Missing keywords can reduce organic search visibility, and the absence of Open Graph metadata will result in generic or incorrect previews when links to the site are shared on platforms like Facebook, Twitter, or LinkedIn.
    Fix: Revert the changes to the metadata object in web/src/app/layout.tsx to restore the description, keywords, and openGraph properties.

  • Issue: Potential withBasePath Regression for Links
    In web/src/app/page.tsx, the link for "Join Us to Contribute" has been changed from href={withBasePath("/contribute")} to href="/contribute".

    --- a/web/src/app/page.tsx
    +++ b/web/src/app/page.tsx
    @@ -961,7 +983,7 @@ export default function Home() {
             <a href="#programs">Programs</a>
             <a href="#screenshots">Screenshots</a>
             <a href="#contact">Contact</a>
    -        <a href={withBasePath("/contribute")}>Join Us &amp; Contribute</a>
    +        <a href="/contribute">Join Us & Contribute</a>
           </div>

    Impact: If the application is deployed to a subdirectory (e.g., https://example.com/my-app/), withBasePath is crucial for generating correct absolute paths. Removing it will cause this specific link to break, leading to a 404 error in production environments that rely on a base path.
    Fix: Revert the href attribute for the "Join Us to Contribute" link in web/src/app/page.tsx to use withBasePath("/contribute") to ensure correct path resolution in all deployment scenarios.

  • Issue: Excessive Use of !important in CSS
    Both web/src/app/globals.css and web/src/app/globals2.css are heavily littered with !important flags, particularly in the dark mode specific styles. This is evident in almost every new dark mode rule.

    /* Examples from diff */
    .dark body { background: var(--bg-white) !important; color: var(--text-dark) !important; }
    .dark .feature-card.mod-card:hover { background-color: rgba(255, 255, 255, 0.04) !important; }
    .dark .contact-section { background-color: hsl(222.2 84% 4.9%) !important; }

    Impact: Over-reliance on !important creates a highly rigid and unmaintainable stylesheet. It makes it extremely difficult to introduce new styles or modify existing ones without resorting to even more !important rules, leading to specificity wars, increased bundle size, and a poor developer experience. It indicates a fundamental issue with how styles are being applied or structured, often bypassing proper CSS specificity and cascade principles.
    Fix: This requires a significant refactor of the CSS.

    1. Leverage CSS Variables: Ensure all theme-related properties (colors, backgrounds, borders) are driven by CSS variables defined in the :root (light theme) and .dark (dark theme) selectors.
    2. Refactor Specificity: Instead of !important, use more specific selectors or ensure the theme variables are applied at the correct level. For example, if .feature-card.mod-card needs a background, it should use background-color: var(--color-surface-card); and var(--color-surface-card) should be defined in both light and dark theme blocks.
    3. Tailwind Integration: For components primarily styled with Tailwind, ensure the dark: prefix is used correctly with Tailwind classes, leveraging the darkMode: "class" configuration. For custom components, ensure they consume the CSS variables.
    4. Example (Conceptual):
      • Instead of:
        .dark .feature-card.mod-card {
          background-color: #1e293b !important;
          color: #ffffff !important;
        }
      • It should be:
        .feature-card.mod-card {
          background-color: var(--color-surface-card); /* Defined in :root and .dark */
          color: var(--color-foreground); /* Defined in :root and .dark */
        }
        /* In :root (light theme) */
        :root {
          --color-surface-card: #ffffff;
          --color-foreground: hsl(222.2 84% 4.9%);
        }
        /* In .dark (dark theme) */
        .dark {
          --color-surface-card: #1e293b;
          --color-foreground: hsl(210 40% 98%);
        }
      This allows the theme provider to switch the class, and the variables automatically update the styles without !important.

🟡 Medium Severity

  • Issue: Inconsistent CSS Variable Naming Conventions
    web/src/app/globals.css introduces dark mode variables like --text-dark, --bg-white, --shadow-sm, etc. In contrast, web/src/app/globals2.css uses a different, more shadcn/ui-like convention with --color-background, --color-foreground, --color-card, --color-surface-secondary, etc.
    Impact: This inconsistency makes the codebase harder to understand, maintain, and extend. Developers need to remember two different naming conventions for theme variables, increasing cognitive load and potential for errors.
    Fix: Standardize on a single, consistent naming convention for all CSS variables, preferably the --color- prefix used in globals2.css which aligns better with modern CSS practices and component libraries like shadcn/ui. Refactor the variables in globals.css accordingly.

  • Issue: Inline Styles for Layout Adjustment
    In web/src/app/page.tsx, an inline style style={{paddingTop: '80px'}} is added to the .hero-content div.

    --- a/web/src/app/page.tsx
    +++ b/web/src/app/page.tsx
    @@ -512,7 +529,7 @@ export default function Home() {
    
       {/* ── Hero ── */}
       <section className="hero">
    -    <div className="container hero-content scroll-reveal">
    +    <div className="container hero-content scroll-reveal" style={{paddingTop: '80px'}}>
           <h1>Empowering Wellness Through Global Community</h1>
           <p>UltimateHealth is a platform that lets you publish health knowledge in your own language, review content, and share podcasts with the world.</p>
         </PageWrapper>

    Impact: Inline styles bypass CSS stylesheets, making them difficult to override, track, and manage. They reduce component reusability and make it harder to maintain a consistent design system, especially across different themes or responsive breakpoints.
    Fix: Move this style into a dedicated CSS class (e.g., .hero-content-padded) in globals2.css or use a Tailwind utility class (e.g., pt-20 if 80px corresponds to Tailwind's p-20) directly on the element.

  • Issue: Mixed Styling Approaches for feature-card Hover State
    In web/src/app/page.tsx, the feature-card mod-card element has both custom CSS classes and inline Tailwind classes for its hover effect.

    --- a/web/src/app/page.tsx
    +++ b/web/src/app/page.tsx
    @@ -706,10 +723,13 @@ export default function Home() {
             ].map((f, i) => (
               <div 
    -          className="feature-card mod-card w-full fade-in" key={i}>
    +          className="feature-card mod-card w-full fade-in border-2 border-transparent transition-all duration-300 ease-in-out hover:border-[#8952c4] hover:bg-muted/40 dark:hover:bg-muted/10 cursor-pointer" 
    +          key={i}
    +        >
                 <div className="mod-icon"><i className={`fas ${f.icon}`}></i></div>
                 <h3>{f.title}</h3>
                 <p>{f.desc}</p>
               </div>
             ))}

    The globals2.css already defines hover states for .feature-card.mod-card for both light and dark modes.
    Impact: This creates a confusing and potentially conflicting styling strategy. It's unclear which styles take precedence, making debugging difficult and increasing the likelihood of unintended visual behavior. It also violates the principle of separation of concerns.
    Fix: Remove the inline Tailwind classes (border-2 border-transparent transition-all duration-300 ease-in-out hover:border-[#8952c4] hover:bg-muted/40 dark:hover:bg-muted/10 cursor-pointer) from the JSX. Ensure that all styling for .feature-card.mod-card, including its hover states, is consistently defined within globals2.css using CSS variables for theme adaptation.

  • Issue: Ambiguous Role of globals.css and globals2.css
    The project uses two global CSS files, web/src/app/globals.css and web/src/app/globals2.css, both of which are modified in this PR to include dark mode styles. The original layout.tsx comment // DO NOT remove the globals2.css import, it contains important global styles for the application suggests globals2.css is primary, yet globals.css is also present and modified.
    Impact: Having two global CSS files without clear documentation or separation of concerns can lead to confusion, redundant styles, and potential conflicts. Developers might not know where to add new global styles or debug existing ones.
    Fix: Consolidate all global styles into a single globals.css file. If there is a strong architectural reason to keep them separate (e.g., one for Tailwind base, one for custom components), this should be clearly documented, and their responsibilities strictly defined to avoid overlap and confusion.

🟢 Low Severity / Nits

  • Issue: Commented-out Code in theme-provider.tsx
    The web/src/components/theme-provider.tsx file contains a block of commented-out code.

    --- a/web/src/components/theme-provider.tsx
    +++ b/web/src/components/theme-provider.tsx
    @@ -0,0 +1,24 @@
    +// "use client"
    +//
    +// import * as React from "react"
    +// import { ThemeProvider as NextThemesProvider } from "next-themes"
    +//
    +// export function ThemeProvider({
    +//   children,
    +//   ...props
    +// }: React.ComponentProps<typeof NextThemesProvider>) {
    +//   return <NextThemesProvider {...props}>{children}</NextThemesProvider>
    +// }
    +
    +// src/components/theme-provider.tsx
    +"use client"
    +
    +import * as React from "react"
    import { ThemeProvider as NextThemesProvider } from "next-themes"
    +
    +export function ThemeProvider({
    +  children,
    +  ...props
    +}: React.ComponentProps<typeof NextThemesProvider>) {
    +  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
    +}

    Impact: Clutters the codebase and can be misleading.
    Fix: Remove the commented-out code block from web/src/components/theme-provider.tsx.

  • Issue: Missing Newline at End of File
    web/src/app/globals.css and web/src/app/globals2.css are missing a newline character at the end of the file.
    Impact: Minor, but can cause issues with some build tools, linters, or version control systems (e.g., diff tools might show an extra line change).
    Fix: Add a newline character at the very end of web/src/app/globals.css and web/src/app/globals2.css.

What's Good ✅

  1. Correct next-themes Integration: The PR correctly sets up next-themes by wrapping the root layout with ThemeProvider, configuring attribute="class", defaultTheme="system", and enableSystem. The ModeToggle component is also well-implemented, including the mounted state to prevent hydration mismatches.
  2. Effective FOUC Prevention: The inclusion of suppressHydrationWarning on the <html> tag is a crucial and correctly applied technique to prevent the "Flash of Unstyled Content" that often occurs with theme switching in Next.js applications.
  3. Improved Color System (HSL): The transition from oklch to hsl for color definitions in globals2.css is a positive step. HSL is generally more widely understood, supported, and easier to manipulate programmatically than oklch, aligning better with modern CSS practices and shadcn/ui conventions.

Verdict

Request Changes

The PR introduces several high-severity issues, most notably the loss of critical SEO/Open Graph metadata, a potential regression for base path-aware links, and a pervasive over-reliance on !important in the CSS, which severely impacts maintainability. These issues must be addressed before merging.

@SB2318 SB2318 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 High Severity

SEO Metadata Regression

The PR removes existing keywords and openGraph metadata from layout.tsx and replaces the detailed description with a generic placeholder.

This is a significant SEO and social-sharing regression that appears unrelated to the dark mode implementation.

Please restore the original metadata unless there is a documented reason for changing it.


Base Path Compatibility Regression

The "Join Us & Contribute" link has been changed from:

href={withBasePath("/contribute")}

to:

href="/contribute"

This may break deployments hosted under a custom base path. Please keep withBasePath() usage to ensure compatibility across deployment environments.

🟡 Medium Severity

Inconsistent Theme Variable Naming


Inline Style Added to Hero Section

The following inline style was introduced:

style={{ paddingTop: "80px" }}

Inline styles make maintenance and theme customization more difficult.

Please move this into a CSS class or Tailwind utility class.


Mixed Styling Approaches

feature-card now combines:

  • Custom CSS definitions
  • Tailwind hover classes

Styling for the same component is now split across multiple locations, making future maintenance harder.

Consider keeping hover behavior in a single place for consistency.


🟢 Low Severity

Remove Commented-Out Code

theme-provider.tsx contains an entire commented implementation above the active implementation.

Please remove unused commented code to keep the file clean.

✅ Positive Feedback

Nice work on the overall dark mode setup:

  • Proper integration of next-themes
  • Correct use of ThemeProvider
  • suppressHydrationWarning added to prevent hydration mismatches
  • Theme toggle implementation follows recommended patterns
  • System theme support is configured correctly

The dark mode foundation looks solid. The primary concerns are around maintainability, SEO regressions, and styling architecture that should be addressed before merging.

Verdict

Request Changes

The dark mode feature itself is a valuable addition, but the SEO metadata removal, base path regression risk, and extensive use of !important introduce maintainability and production concerns that should be resolved before merge.

@karkera-saakshi karkera-saakshi force-pushed the feature/dark-mode-toggle branch from 81a102f to 0db4694 Compare June 5, 2026 07:19
@karkera-saakshi

Copy link
Copy Markdown
Author

Hi @SB2318, I have addressed all the high severity issues:
✅ SEO metadata restored in layout.tsx
✅ withBasePath link restored in page.tsx
✅ Inline style moved to .hero-content-padded CSS class
✅ Removed Tailwind hover classes from feature-card — styling now consolidated in CSS
✅ Replaced !important with CSS variables where safe (feature-card, screenshot-details)
Regarding remaining !important flags — these are necessary to override existing styles in globals.css which uses hardcoded values. Removing them without refactoring globals.css would break the dark mode styling. I am happy to refactor globals.css in a follow-up PR if preferred.
Please let me know if further changes are needed!

@karkera-saakshi karkera-saakshi force-pushed the feature/dark-mode-toggle branch from 0db4694 to 945296b Compare June 5, 2026 07:25
@rushiii3

rushiii3 commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

@karkera-saakshi

You modified global2.css, which affects all UI components across the application. Please restore it to its original/default state.

If you want to support dark mode, there's no need to rewrite global styles or update every component's CSS. Tailwind already provides built-in dark mode utilities. You can simply add classes like:

className="bg-white dark:bg-black"

or

className="text-black dark:text-white"

and apply them only where needed. This keeps dark mode changes component-scoped and avoids introducing global styling changes that impact the entire UI.

Tailwind's recommended approach is to use the dark: variant rather than maintaining separate global CSS overrides.

Reference: https://tailwindcss.com/docs/dark-mode

@SB2318

SB2318 commented Jun 6, 2026

Copy link
Copy Markdown
Owner

@karkera-saakshi please consider @rushiii3 's Point, and please update. I am available.

@karkera-saakshi

Copy link
Copy Markdown
Author

Hi @SB2318 I am available and working on it.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Automated Review Feedback

Provide actionable comments grouped by severity:

Critical

  1. Metadata Loss in layout.tsx (Persisting): The metadata object in web/src/app/layout.tsx still lacks keywords and openGraph properties, and the description is generic. This is a significant regression for Search Engine Optimization (SEO) and social media sharing.
    • Actionable: Revert the changes to the metadata object in web/src/app/layout.tsx to restore the original description, keywords, and openGraph properties.
  2. Excessive Use of !important in CSS (Worsened): The web/src/app/globals2.css file is now heavily reliant on !important flags for dark mode styles. This creates a highly rigid and unmaintainable stylesheet, making it extremely difficult to introduce new styles or modify existing ones without resorting to more !important rules. This issue was highlighted in the previous review and has been exacerbated.
    • Actionable: Refactor the CSS in web/src/app/globals2.css to remove all !important flags. Leverage CSS variables defined in :root (for light theme) and .dark (for dark theme) selectors for all theme-dependent properties. Ensure Tailwind's dark: prefix is used for Tailwind classes, and custom CSS consumes the defined CSS variables.
  3. Missing /medical-glossary link in mobile navigation (New Regression): The Link component for /medical-glossary has been removed from the mobile navigation in web/src/app/page.tsx. This removes user access to a potentially important page.
    • Actionable: Restore the /medical-glossary link in the mobile navigation (<nav className="mobile-nav">) in web/src/app/page.tsx. Ensure it uses Link from next/link and onClick={() => setMobileMenuOpen(false)} for proper navigation and menu closure.

Important

  1. Ambiguous Role of globals.css and globals2.css (Persisting): Both web/src/app/globals.css and web/src/app/globals2.css are present and modified with dark mode styles. This creates confusion, potential for redundant styles, and makes maintenance difficult.
    • Actionable: Consolidate all global styles into a single globals.css file. If there is a strong architectural reason to keep them separate, clearly document their responsibilities and ensure no overlapping concerns.
  2. Inconsistent CSS Variable Naming Conventions (Persisting): web/src/app/globals.css uses --text-dark, --bg-white, etc., while web/src/app/globals2.css uses shadcn/ui-aligned --color-background, --color-foreground, etc. This inconsistency hinders maintainability and readability across the codebase.
    • Actionable: Standardize on a single, consistent naming convention for all CSS variables, preferably the shadcn/ui-aligned --color- prefix. Refactor variables in globals.css accordingly after consolidating the global styles.
  3. Potential Loss of Tailwind radius and shadow variables (New Concern): The @theme inline block in globals2.css that defined radius-sm, radius-lg, radius-xl, shadow-*, and tracking-* variables has been removed. While radius-md is now directly defined, it's unclear if other radius, shadow, and tracking variables are still correctly applied or if their removal will lead to unintended styling changes elsewhere in the application.
    • Actionable: Verify that the removal of radius-sm, radius-lg, radius-xl, shadow-*, and tracking-* definitions from globals2.css does not negatively impact existing components. If these variables are used, ensure they are either correctly inherited from Tailwind's default configuration, redefined consistently, or replaced with appropriate Tailwind utility classes.

Suggestions

  1. Missing Newline at End of File (Persisting): web/src/app/globals.css and web/src/app/globals2.css are still missing a newline character at the end of the file.
    • Actionable: Add a newline character at the very end of web/src/app/globals.css and web/src/app/globals2.css.

Maintainer Note:

Once the initial automated feedback has been addressed, maintainer @SB2318 will review the pull request for final evaluation.

@karkera-saakshi

Copy link
Copy Markdown
Author

Hi @SB2318 @rushiii3 ,

I lost my progress on the dark mode implementation as I hadn't committed my changes to Git in time. I've started working on it again and will submit the final PR within 2–3 days. Also I am making small changes in global.css (commenting only background and text colors) because it was overridding the tailwind dark mode utilities and included the commented part of global.css using tailwind.

Thank you for your patience!

@karkera-saakshi

Copy link
Copy Markdown
Author

Hey @SB2318, I've completed the assigned issue — implemented dark mode toggle with localStorage persistence on the main page using Tailwind CSS dark: utilities. Please review when you get a chance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants